class SimpleHTMLParser {
  private html: string;

  constructor(html: string) {
      this.html = html;
  }

  parse(): HTMLElementNode[] {
      const stack: HTMLElementNode[] = [];
      const result: HTMLElementNode[] = [];
      let currentIndex = 0;
      let length = this.html.length;

      while (currentIndex < length) {
          const char = this.html[currentIndex];

          if (char === '<') {
              const endIndex = this.findClosingTagIndex(currentIndex);
              const tagContent = this.html.substring(currentIndex, endIndex + 1);
              const tagName = this.extractTagName(tagContent);

              if (tagName) {
                  if (tagContent.endsWith('/>')) {
                      // Self-closing tag
                      result.push({ tagName, textContent: '' });
                  } else {
                      // Opening tag
                      const element: HTMLElementNode = { tagName, textContent: '' };
                      stack.push(element);
                      currentIndex = endIndex; // Move index to the end of the tag
                  }
              }
          } else if (stack.length > 0) {
              // Collect text content until we hit another tag
              const nextTagStartIndex = this.html.indexOf('<', currentIndex);
              if (nextTagStartIndex === -1) {
                  // No more tags, collect the rest of the string
                  stack[stack.length - 1].textContent += this.html.substring(currentIndex);
                  currentIndex = length;
              } else {
                  stack[stack.length - 1].textContent += this.html.substring(currentIndex, nextTagStartIndex);
                  currentIndex = nextTagStartIndex - 1; // Move back one step to process the new tag
              }
          } else {
              currentIndex++;
          }
      }

      // Close any remaining open tags (simplistic handling of missing closing tags)
      while (stack.length > 0) {
          result.push(stack.pop()!);
      }

      return result;
  }

  private findClosingTagIndex(startIndex: number): number {
      let balance = 1;
      let currentIndex = startIndex + 1;
      const length = this.html.length;

      while (currentIndex < length) {
          const char = this.html[currentIndex];

          if (char === '<') {
              balance++;
          } else if (char === '>') {
              balance--;
              if (balance === 0) {
                  return currentIndex;
              }
          }

          currentIndex++;
      }

      throw new Error('Unclosed tag found');
  }

  private extractTagName(tagContent: string): string | null {
      const tagNameMatch = tagContent.match(/^<([a-zA-Z]+)/);
      return tagNameMatch ? tagNameMatch[1] : null;
  }
}

interface HTMLElementNode {
  tagName: string;
  textContent: string;
}

// Example usage:
const html = `
<html>
  <head><title>Test Page</title></head>
  <body>
      <h1>Hello, World!</h1>
      <p>This is a <b>bold</b> paragraph.</p>
  </body>
</html>
`;

const parser = new SimpleHTMLParser(html);
const parsedNodes = parser.parse();

console.log(parsedNodes);